
/* GCSx
** WORLDEDIT.CPP
**
** World storage
** EDITOR members
*/

/*****************************************************************************
** Copyright (C) 2003-2006 Janson
**
** This program is free software; you can redistribute it and/or modify
** it under the terms of the GNU General Public License as published by
** the Free Software Foundation; either version 2 of the License, or
** (at your option) any later version.
** 
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
**
** You should have received a copy of the GNU General Public License
** along with this program; if not, write to the Free Software
** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111, USA.
*****************************************************************************/

#include "all.h"

set<WorldEdit*>* WorldEdit::allWorlds = NULL;

WorldEdit::WorldEdit() throw_File : World(), layersById(), spawnsById(), foldersById(), undo(this) { start_func
    headerModified = 1; // We have a title!
    contentModified = 1;
    browserNode = NULL;
    saveFile = NULL;
    file = saveFile = new WorldFile(); // Only exception point
    
    saveFile->newBlock(WorldFile::BLOCKTYPE_WORLDINFO, this);
    if (!allWorlds) allWorlds = new set<WorldEdit*>;
    allWorlds->insert(this);

    desktop->broadcastObjChange(OBJ_WORLD | OBJMOD_CREATE, this, 0, 0);
}

WorldEdit::WorldEdit(const char* wFilename) throw_File : World(), layersById(), spawnsById(), undo(this) { start_func
    headerModified = contentModified = 0;
    browserNode = NULL;
    saveFile = NULL;
    filename = NULL;
    TileSetEdit* tileset = NULL;
    SceneEdit* scene = NULL;
    AnimGroupEdit* animgroup = NULL;
    ScriptEdit* script = NULL;
    FolderEdit* folder = NULL;

    try {
        filename = new string(wFilename);
        file = saveFile = new WorldFile(filename); // One of many exception points (+claimBlocks)

        Uint32 type;
        Uint32 id;

        // Loop pass 1- World Info and TileSets
        saveFile->scanBlocks();
        while (saveFile->nextBlock(&type, &id)) {
            switch (type) {
                case WorldFileLoad::BLOCKTYPE_WORLDINFO:
                    saveFile->claimBlock(id, this);
                    break;

                case WorldFileLoad::BLOCKTYPE_TILESET:
                    tileset = new TileSetEdit(this, 0);
                    saveFile->claimBlock(id, tileset);
                    indexTileSet(tileset);
                    tileset = NULL;
                    break;
            }
        }

        // Loop pass 2- Scenes and Animation Groups (need tilesets)
        saveFile->scanBlocks();
        while (saveFile->nextBlock(&type, &id)) {
            switch (type) {
                case WorldFileLoad::BLOCKTYPE_ANIMGROUP:
                    animgroup = new AnimGroupEdit(this);
                    saveFile->claimBlock(id, animgroup);
                    indexAnimGroup(animgroup);
                    animgroup = NULL;
                    break;
            }
        }
        
        // Loop pass 3- Scripts (need tilesets/animgroups)
        saveFile->scanBlocks();
        while (saveFile->nextBlock(&type, &id)) {
            switch (type) {
                case WorldFileLoad::BLOCKTYPE_SCRIPT:
                    script = new ScriptEdit(this);
                    saveFile->claimBlock(id, script);
                    indexScript(script);
                    script = NULL;
                    break;
            }
        }

        // Loop pass 4- Scenes (need tilesets/animgroups/scripts for spawns)
        saveFile->scanBlocks();
        while (saveFile->nextBlock(&type, &id)) {
            switch (type) {
                case WorldFileLoad::BLOCKTYPE_SCENE:
                    scene = new SceneEdit(this);
                    saveFile->claimBlock(id, scene);
                    indexScene(scene);
                    scene = NULL;
                    break;
            }
        }
        
        // Loop pass 5- Folders (need all items that might go in a folder)
        saveFile->scanBlocks();
        while (saveFile->nextBlock(&type, &id)) {
            switch (type) {
                case WorldFileLoad::BLOCKTYPE_FOLDER:
                    folder = new FolderEdit(this);
                    saveFile->claimBlock(id, folder);
                    indexFolder(folder);
                    folder = NULL;
                    break;
            }
        }

        if (!allWorlds) allWorlds = new set<WorldEdit*>;
        allWorlds->insert(this);
    }
    catch (...) {
        // Temporaries
        delete tileset;
        delete scene;
        delete animgroup;
        delete script;
        delete folder;
        
        // Collections
        deleteCollections();    
        
        // File
        delete saveFile;
        delete filename;
        file = saveFile = NULL;
        filename = NULL;
        throw;
    }

    desktop->broadcastObjChange(OBJ_WORLD | OBJMOD_CREATE, this, 0, 0);
}

WorldEdit::~WorldEdit() { start_func
    // May not be needed, but a good safety
    desktop->broadcastObjChange(OBJ_WORLD | OBJMOD_DELETE, this, 0, 0);

    // Our node must be deleted outside this destructor
    
    if (allWorlds) {
        allWorlds->erase(this);
        if (allWorlds->empty()) {
            delete allWorlds;
            allWorlds = NULL;
        }
    }
}

void WorldEdit::addToBrowser(TreeView* worldBrowser) { start_func
    TreeView* myNode = new TreeView(title, this, TREEVIEW_WORLD, treeviewEventWrap);
    myNode->setIcon(3);
    TreeView* subNode = NULL;
    //TreeView* subAddNode = NULL;

    TileSetIndex::iterator tilesetPos;
    TileSetIndex::iterator tilesetEnd = tilesetsById.end();

    SceneIndex::iterator scenePos;
    SceneIndex::iterator sceneEnd = scenesById.end();

    AnimGroupIndex::iterator animgroupPos;
    AnimGroupIndex::iterator animgroupEnd = animgroupsById.end();

    ScriptIndex::iterator scriptPos;
    ScriptIndex::iterator scriptEnd = scriptsById.end();

    FolderIndex::iterator folderPos;
    FolderIndex::iterator folderEnd = foldersById.end();
    
    myNode->expand(1);
    
    subNode = new TreeView("Images", this, TREEVIEW_TILESET, treeviewEventWrap);
    subNode->setIcon(1, 0);
    // Add existing libraries
    for (tilesetPos = tilesetsById.begin(); tilesetPos != tilesetEnd; ++tilesetPos) {
        if (!(*tilesetPos).second->getIsFont()) {
            dynamic_cast<TileSetEdit*>((*tilesetPos).second)->addToBrowser(subNode, 0);
        }
    }
    myNode->insert(subNode, 0);
    
    subNode = new TreeView("Fonts", this, TREEVIEW_FONT, treeviewEventWrap);
    subNode->setIcon(1, 0);
    // Add existing libraries
    for (tilesetPos = tilesetsById.begin(); tilesetPos != tilesetEnd; ++tilesetPos) {
        if ((*tilesetPos).second->getIsFont()) {
            dynamic_cast<TileSetEdit*>((*tilesetPos).second)->addToBrowser(subNode, 0);
        }
    }
    myNode->insert(subNode, 0);
    
    subNode = new TreeView("Scenes", this, TREEVIEW_SCENE, treeviewEventWrap);
    subNode->setIcon(1, 0);
    // Add existing scenes
    for (scenePos = scenesById.begin(); scenePos != sceneEnd; ++scenePos) {
        (dynamic_cast<SceneEdit*>((*scenePos).second))->addToBrowser(subNode, 0);
    }
    myNode->insert(subNode, 0);
    
    subNode = new TreeView("Scripts", this, TREEVIEW_SCRIPT, treeviewEventWrap);
    subNode->setIcon(1, 0);
    // Add existing scripts
    for (scriptPos = scriptsById.begin(); scriptPos != scriptEnd; ++scriptPos) {
        if ((*scriptPos).second->getType() != Script::SCRIPT_NOTE) {
            dynamic_cast<ScriptEdit*>((*scriptPos).second)->addToBrowser(subNode, 0);
        }
    }
    myNode->insert(subNode, 0);
    
    subNode = new TreeView("Animations", this, TREEVIEW_ANIMGROUP, treeviewEventWrap);
    subNode->setIcon(1, 0);
    // Add existing spite libraries
    for (animgroupPos = animgroupsById.begin(); animgroupPos != animgroupEnd; ++animgroupPos) {
        dynamic_cast<AnimGroupEdit*>((*animgroupPos).second)->addToBrowser(subNode, 0);
    }
    myNode->insert(subNode, 0);

    subNode = new TreeView("Notes", this, TREEVIEW_NOTES, treeviewEventWrap);
    subNode->setIcon(1, 0);
    // Add existing notes
    for (scriptPos = scriptsById.begin(); scriptPos != scriptEnd; ++scriptPos) {
        if ((*scriptPos).second->getType() == Script::SCRIPT_NOTE) {
            dynamic_cast<ScriptEdit*>((*scriptPos).second)->addToBrowser(subNode, 0);
        }
    }
    myNode->insert(subNode, 0);
    
    // @TODO: Clearly this block is a temporary setup
    subNode = new TreeView("Folders", this, TREEVIEW_FOLDER, treeviewEventWrap);
    subNode->setIcon(1, 0);
    // Add existing folders
    for (folderPos = foldersById.begin(); folderPos != folderEnd; ++folderPos) {
        dynamic_cast<FolderEdit*>((*folderPos).second)->addToBrowser(subNode, 0);
    }
    myNode->insert(subNode, 0);
        
    worldBrowser->insert(myNode);
    browserNode = myNode;
}

int WorldEdit::deleteTileset(TileSetEdit* toDelete, Window* srcWin) { start_func
    // Scan all scenes/layers, ensure tileset is not in use
    SceneIndex::iterator posS;
    SceneIndex::iterator endS = scenesById.end();
    for (posS = scenesById.begin(); posS != endS; ++posS) {
        for (int count = (*posS).second->getLayerCount() - 1; count >= 0; --count) {
            Layer* layer = (*posS).second->getLayer(count);
            if (layer->getTileSet() == toDelete) {
                guiErrorBox(formatString("Cannot delete %s- used by scene %s", toDelete->getName().c_str(), (*posS).second->getName().c_str()), errorTitleInUse);
                return 0;
            }
        }
    }

    // Scan all animation groups, ensure tileset is not in use
    AnimGroupIndex::iterator posL;
    AnimGroupIndex::iterator endL = animgroupsById.end();
    for (posL = animgroupsById.begin(); posL != endL; ++posL) {
        if ((*posL).second->usesTileSet(toDelete)) {
            guiErrorBox(formatString("Cannot delete %s- used by animation group %s", toDelete->getName().c_str(), (*posL).second->getName().c_str()), errorTitleInUse);
            return 0;
        }
    }

    // Verify tileset is in our list
    TileSet* verify = findTileSet(toDelete->getId());
    if (verify) {
        int noUndo = 0;

        // Remove browser node
        TreeView* node = browserNode->findRecursive(toDelete);
        if (node) node->findParent()->remove(node);

        // Disassociate, delete if fails
        try {
            toDelete->disassociate();
        }
        catch (FileException& e) {
            // File error, object must be deleted anyways, but can't be undone
            guiErrorBox(formatString("Deletion of object will not be undoable- %s", e.details), errorTitleFile);
            undo.clearUndo();
            noUndo = 1;
        }
        
        int noDelete = 0;
        if (!noUndo) {
            try {
                if (undo.storeUndoDelete(UndoBuffer::UNDO_TILEDELETE, toDelete->getId(), toDelete, srcWin))
                    noDelete = 1;
            }
            catch (UndoException& e) {
                toDelete->reassociate(browserNode->find(toDelete->getIsFont() ? "Fonts" : "Images"));
                return 0;
            }
        }

        // Delete it from list, delete it from file
        deindexTileSet(toDelete);
        saveFile->discardBlock(toDelete);
        // Delete entire object if not claimed by undo
        if (!noDelete) delete toDelete;
    }
    
    return 1;
}

int WorldEdit::deleteScene(SceneEdit* toDelete, Window* srcWin) { start_func
    // Verify scene is in our list
    Scene* verify = findScene(toDelete->getId());
    if (verify) {
        int noUndo = 0;

        // Remove browser node
        TreeView* node = browserNode->findRecursive(toDelete);
        if (node) node->findParent()->remove(node);

        // Disassociate, delete if fails
        try {
            toDelete->disassociate();
        }
        catch (FileException& e) {
            // File error, object must be deleted anyways, but can't be undone
            guiErrorBox(formatString("Deletion of object will not be undoable- %s", e.details), errorTitleFile);
            undo.clearUndo();
            noUndo = 1;
        }

        int noDelete = 0;
        if (!noUndo) {
            try {
                if (undo.storeUndoDelete(UndoBuffer::UNDO_SCENEDELETE, toDelete->getId(), toDelete, srcWin))
                    noDelete = 1;
            }
            catch (UndoException& e) {
                toDelete->reassociate(browserNode->find("Scenes"));
                return 0;
            }
        }
            
        // Delete it from list, delete it from file
        deindexScene(toDelete);
        saveFile->discardBlock(toDelete);
        // Delete entire object if not claimed by undo
        if (!noDelete) delete toDelete;
    }
    
    return 1;
}

int WorldEdit::deleteScript(ScriptEdit* toDelete, Window* srcWin) { start_func
    // Verify script is in our list
    Script* verify = findScript(toDelete->getId());
    if (verify) {
        int noUndo = 0;

        // Remove browser node
        TreeView* node = browserNode->findRecursive(toDelete);
        if (node) node->findParent()->remove(node);

        // Disassociate, delete if fails
        try {
            toDelete->disassociate();
        }
        catch (FileException& e) {
            // File error, object must be deleted anyways, but can't be undone
            guiErrorBox(formatString("Deletion of object will not be undoable- %s", e.details), errorTitleFile);
            undo.clearUndo();
            noUndo = 1;
        }

        int noDelete = 0;
        if (!noUndo) {
            try {
                if (undo.storeUndoDelete(UndoBuffer::UNDO_SCRIPTDELETE, toDelete->getId(), toDelete, srcWin))
                    noDelete = 1;
            }
            catch (UndoException& e) {
                toDelete->reassociate(browserNode->find("Scripts"));
                return 0;
            }
        }

        // Delete it from list, delete it from file
        deindexScript(toDelete);
        saveFile->discardBlock(toDelete);
        // Delete entire object if not claimed by undo
        if (!noDelete) delete toDelete;
    }
    
    return 1;
}

int WorldEdit::deleteAnimGroup(AnimGroupEdit* toDelete, Window* srcWin) { start_func
    // Verify animation group is in our list
    AnimGroup* verify = findAnimGroup(toDelete->getId());
    if (verify) {
        int noUndo = 0;

        // Remove browser node
        TreeView* node = browserNode->findRecursive(toDelete);
        if (node) node->findParent()->remove(node);

        // Disassociate, delete if fails
        try {
            toDelete->disassociate();
        }
        catch (FileException& e) {
            // File error, object must be deleted anyways, but can't be undone
            guiErrorBox(formatString("Deletion of object will not be undoable- %s", e.details), errorTitleFile);
            undo.clearUndo();
            noUndo = 1;
        }

        int noDelete = 0;
        if (!noUndo) {
            try {
                if (undo.storeUndoDelete(UndoBuffer::UNDO_ANIMGROUPDELETE, toDelete->getId(), toDelete, srcWin))
                    noDelete = 1;
            }
            catch (UndoException& e) {
                toDelete->reassociate(browserNode->find("Animations"));
                return 0;
            }
        }

        // Delete it from list, delete it from file
        deindexAnimGroup(toDelete);
        saveFile->discardBlock(toDelete);
        // Delete entire object if not claimed by undo
        if (!noDelete) delete toDelete;
    }
    
    return 1;
}

int WorldEdit::deleteFolder(FolderEdit* toDelete, Window* srcWin) { start_func
    // @TODO: Scan folder, ensure empty (if not, prompt user and move items elsewhere)    
    
    // Verify folder is in our list
    FolderEdit* verify = findFolder(toDelete->getId());
    if (verify) {
        int noUndo = 0;

        // Remove browser node
        TreeView* node = browserNode->findRecursive(toDelete);
        if (node) node->findParent()->remove(node);

        // Disassociate, delete if fails
        try {
            toDelete->disassociate();
        }
        catch (FileException& e) {
            // File error, object must be deleted anyways, but can't be undone
            guiErrorBox(formatString("Deletion of object will not be undoable- %s", e.details), errorTitleFile);
            undo.clearUndo();
            noUndo = 1;
        }

        int noDelete = 0;
        if (!noUndo) {
            try {
                if (undo.storeUndoDelete(UndoBuffer::UNDO_FOLDERDELETE, toDelete->getId(), toDelete, srcWin))
                    noDelete = 1;
            }
            catch (UndoException& e) {
                toDelete->reassociate(browserNode->find("Folders"));
                return 0;
            }
        }

        // Delete it from list, delete it from file
        deindexFolder(toDelete);
        saveFile->discardBlock(toDelete);
        // Delete entire object if not claimed by undo
        if (!noDelete) delete toDelete;
    }
    
    return 1;
}

void WorldEdit::importTileSet(TileSetEdit* toAdd, int openWindow, Window* srcWin) { start_func
    try {
        indexTileSet(toAdd);
        // (our only exception point)
        undo.storeUndoCreate(UndoBuffer::UNDO_TILECREATE, toAdd->getId(), srcWin);
        toAdd->addToBrowser(browserNode->find(toAdd->getIsFont() ? "Fonts" : "Images"));
        if (openWindow) toAdd->openBrowseWindow();
        saveFile->newBlock(WorldFile::BLOCKTYPE_TILESET, toAdd);
    }
    catch (UndoException& e) {
        delete toAdd;
    }
}

void WorldEdit::importScene(SceneEdit* toAdd, int openWindow, Window* srcWin) { start_func
    try {
        indexScene(toAdd);
        // (our only exception point)
        undo.storeUndoCreate(UndoBuffer::UNDO_SCENECREATE, toAdd->getId(), srcWin);
        toAdd->addToBrowser(browserNode->find("Scenes"));
        if (openWindow) toAdd->openEditWindow();
        saveFile->newBlock(WorldFile::BLOCKTYPE_SCENE, toAdd);
    }
    catch (UndoException& e) {
        delete toAdd;
    }
}

void WorldEdit::readdTileset(TileSetEdit* toAdd) { start_func
    indexTileSet(toAdd);
    saveFile->newBlock(WorldFile::BLOCKTYPE_TILESET, toAdd);
    toAdd->reassociate(browserNode->find(toAdd->getIsFont() ? "Fonts" : "Images"));
}

void WorldEdit::readdScene(SceneEdit* toAdd) { start_func
    indexScene(toAdd);
    saveFile->newBlock(WorldFile::BLOCKTYPE_SCENE, toAdd);
    toAdd->reassociate(browserNode->find("Scenes"));
}

void WorldEdit::readdScript(ScriptEdit* toAdd) { start_func
    indexScript(toAdd);
    saveFile->newBlock(WorldFile::BLOCKTYPE_SCRIPT, toAdd);
    toAdd->reassociate(browserNode->find("Scripts"));
}

void WorldEdit::readdAnimGroup(AnimGroupEdit* toAdd) { start_func
    indexAnimGroup(toAdd);
    saveFile->newBlock(WorldFile::BLOCKTYPE_ANIMGROUP, toAdd);
    toAdd->reassociate(browserNode->find("Animations"));
}

void WorldEdit::readdFolder(FolderEdit* toAdd) { start_func
    indexFolder(toAdd);
    saveFile->newBlock(WorldFile::BLOCKTYPE_FOLDER, toAdd);
    toAdd->reassociate(browserNode->find("Folders"));
}

int WorldEdit::commandEvent(int code) { start_func
    switch (code) {
        case FILE_SAVE:
            save();
            return 1;
        
        case FILE_SAVEAS:
            saveAs();
            return 1;

        case FILE_CLOSE:
            close();
            return 1;

        case EDIT_UNDO:
            undo.undoPerform();
            return 1;
            
        case EDIT_REDO:
            undo.redoPerform();
            return 1;
            
        case NEW_SCENE: {
            SceneEdit* myScene = new SceneEdit(this, unusedSceneId());
            SceneEdit* toDelete = myScene;
            indexScene(myScene);
            try {
                undo.preUndoBlock();
                if (undo.storeUndoCreate(UndoBuffer::UNDO_SCENECREATE, myScene->getId(), desktop->findPreviousFocusWindow())) toDelete = NULL;

                // If not OK, cancel creation of scene
                if (!myScene->propertiesDialog()) {
                    // (this may delete and send msg)
                    undo.cancelUndoBlock();
                    // (if not, this does)
                    if (toDelete) {
                        undo.disableUndo();
                        deleteScene(toDelete);
                        undo.enableUndo();
                    }
                    return 1;
                }
                
                undo.postUndoBlock();

                myScene->addToBrowser(browserNode->find("Scenes"));
                myScene->openEditWindow();
                
                saveFile->newBlock(WorldFile::BLOCKTYPE_SCENE, myScene);
            }
            catch (UndoException& e) {
                delete toDelete;
                return 1;
            }
            return 1;
        }

        case NEW_ANIM: {
            AnimGroupEdit* myAnimGroup = new AnimGroupEdit(this, unusedAnimGroupId());
            AnimGroupEdit* toDelete = myAnimGroup;
            indexAnimGroup(myAnimGroup);
            try {
                undo.preUndoBlock();
                if (undo.storeUndoCreate(UndoBuffer::UNDO_ANIMGROUPCREATE, myAnimGroup->getId(), desktop->findPreviousFocusWindow())) toDelete = NULL;

                // If not OK, cancel creation of anim group
                if (!myAnimGroup->propertiesDialog()) {
                    // (this may delete and send msg)
                    undo.cancelUndoBlock();
                    // (if not, this does)
                    if (toDelete) {
                        undo.disableUndo();
                        deleteAnimGroup(toDelete);
                        undo.enableUndo();
                    }
                    return 1;
                }
                
                undo.postUndoBlock();

                myAnimGroup->addToBrowser(browserNode->find("Animations"));
                myAnimGroup->openBrowseWindow();
                
                saveFile->newBlock(WorldFile::BLOCKTYPE_ANIMGROUP, myAnimGroup);
            }
            catch (UndoException& e) {
                delete toDelete;
                return 1;
            }
            return 1;
        }

        case NEW_FOLDER: {
            FolderEdit* myFolder = new FolderEdit(this, unusedFolderId());
            FolderEdit* toDelete = myFolder;
            indexFolder(myFolder);
            try {
                undo.preUndoBlock();
                if (undo.storeUndoCreate(UndoBuffer::UNDO_FOLDERCREATE, myFolder->getId(), desktop->findPreviousFocusWindow())) toDelete = NULL;

                // If not OK, cancel creation of folder
                if (!myFolder->propertiesDialog()) {
                    // (this may delete and send msg)
                    undo.cancelUndoBlock();
                    // (if not, this does)
                    if (toDelete) {
                        undo.disableUndo();
                        deleteFolder(toDelete);
                        undo.enableUndo();
                    }
                    return 1;
                }
                
                undo.postUndoBlock();

                myFolder->addToBrowser(browserNode->find("Folders"));
                
                saveFile->newBlock(WorldFile::BLOCKTYPE_FOLDER, myFolder);
            }
            catch (UndoException& e) {
                delete toDelete;
                return 1;
            }
            return 1;
        }

        case NEW_NOTES:
        case NEW_SCRIPT:
        case NEW_LIBRARY: {
            Script::ScriptType type = code == NEW_NOTES ? Script::SCRIPT_NOTE : (code == NEW_SCRIPT ? Script::SCRIPT_CODE : Script::SCRIPT_LIBRARY);
            ScriptEdit* myScript = new ScriptEdit(this, type, unusedScriptId());
            ScriptEdit* toDelete = myScript;
            indexScript(myScript);
            try {
                undo.preUndoBlock();
                if (undo.storeUndoCreate(UndoBuffer::UNDO_SCRIPTCREATE, myScript->getId(), desktop->findPreviousFocusWindow())) toDelete = NULL;

                // If not OK, cancel creation of script
                if (!myScript->propertiesDialog()) {
                    // (this may delete and send msg)
                    undo.cancelUndoBlock();
                    // (if not, this does)
                    if (toDelete) {
                        undo.disableUndo();
                        deleteScript(toDelete);
                        undo.enableUndo();
                    }
                    return 1;
                }
                
                undo.postUndoBlock();

                myScript->addToBrowser(browserNode->find(code == NEW_NOTES ? "Notes" : "Scripts"));
                myScript->openEditWindow();
                
                saveFile->newBlock(WorldFile::BLOCKTYPE_SCRIPT, myScript);
            }
            catch (UndoException& e) {
                delete toDelete;
                return 1;
            }
            return 1;
        }
            
        case NEW_IMPORT_IMAGE: {
            ImportImgDialog::createTileSet()->run(this, desktop->findPreviousFocusWindow());
            return 1;
        }
            
        case NEW_IMPORT_FONT: {
            ImportImgDialog::createFontSet()->run(this, desktop->findPreviousFocusWindow());
            return 1;
        }
    
        case NEW_IMAGE:
        case NEW_FONT: {
            TileSetEdit* myTileSet = new TileSetEdit(this, code == NEW_FONT ? 1 : 0, unusedTileSetId());
            TileSetEdit* toDelete = myTileSet;
            indexTileSet(myTileSet);
            try {
                undo.preUndoBlock();
                if (undo.storeUndoCreate(UndoBuffer::UNDO_TILECREATE, myTileSet->getId(), desktop->findPreviousFocusWindow())) toDelete = NULL;

                // If not OK, cancel creation of tile set
                if (!myTileSet->propertiesDialog(1)) {
                    // (this may delete and send msg)
                    undo.cancelUndoBlock();
                    // (if not, this does)
                    if (toDelete) {
                        undo.disableUndo();
                        deleteTileset(toDelete);
                        undo.enableUndo();
                    }
                    return 1;
                }

                undo.postUndoBlock();

                myTileSet->addToBrowser(browserNode->find(code == NEW_FONT ? "Fonts" : "Images"));
                myTileSet->openBrowseWindow();
                
                saveFile->newBlock(WorldFile::BLOCKTYPE_TILESET, myTileSet);
            }
            catch (UndoException& e) {
                delete toDelete;
                return 1;
            }
            return 1;
        }
    }
            
    return 0;
}


Window::CommandSupport WorldEdit::supportsCommand(int code) const { start_func
    switch (code) {
        case FILE_SAVE:
        case FILE_SAVEAS:
        case FILE_CLOSE:
            return Window::COMMAND_ENABLE;

        case EDIT_UNDO:
            if (undo.undoAvailable()) return Window::COMMAND_ENABLE;
            return Window::COMMAND_DISABLE;
            
        case EDIT_REDO:
            if (undo.redoAvailable()) return Window::COMMAND_ENABLE;
            return Window::COMMAND_DISABLE;
    }
    
    return Window::COMMAND_HIDE;
}

int WorldEdit::treeviewEvent(int code, int command, int check) { start_func
    if (check) {
        return supportsCommand(command);
    }

    switch (code) {
        case TREEVIEW_WORLD:
            switch (command) {
                case LV_RCLICK:
                    // @TODO: Should actually be a popup with this as an item
                    propertiesDialog(desktop->findPreviousFocusWindow());
                    return 1;

                case LV_DELETE:
                    close();
                    return 1;
            }
            
            return commandEvent(command);
    }
    
    return 0;
}

int WorldEdit::treeviewEventWrap(void* ptr, int code, int command, int check) { start_func
    return ((WorldEdit*)ptr)->treeviewEvent(code, command, check);
}

int WorldEdit::close(int justSave, int dontAsk) { start_func
    // Modified?
    if ((saveFile->isModified()) && (!dontAsk)) {
        int result = guiMessageBox(formatString("World '%s' has been modified- save?", title.c_str()), "World Modified", &messageBoxYes, &messageBoxNo, &messageBoxCancel);
        if ((result == 3) || (result == 0)) return 0;
        if (result == 1) {
            if (!save()) return 0;
        }
    }
    
    if (justSave) return 1;
    
    // Delete browser node from world browser
    // Also deletes, ex: tileset browser nodes
    if (browserNode) {
        browserNode->findParent()->remove(browserNode);
    }    
    
    // Delete
    delete this;
    return 1;
}

int WorldEdit::modifiedAll() { start_func
    if (!allWorlds) return 1;
    
    set<WorldEdit*>::iterator pos;
    set<WorldEdit*>::iterator end = allWorlds->end();

    for (pos = allWorlds->begin(); pos != end; ++pos) {
        assert(*pos);
        
        // Close it; cancel rest of process anytime cancel is hit 
        if (!(*pos)->close(1)) return 0;
    }
    
    return 1;
}

void WorldEdit::closeAll(int dontAsk) { start_func
    if (!allWorlds) return;
    if ((!dontAsk) && (!modifiedAll())) return;

    set<WorldEdit*>::iterator pos;
    set<WorldEdit*>::iterator next;
    set<WorldEdit*>::iterator end = allWorlds->end();

    for (pos = allWorlds->begin(); pos != end; pos = next) {
        assert(*pos);
        
        // Note the next element in sequence now, before we close it
        next = pos;
        ++next;
        
        // Close it; no questions asked
        (*pos)->close(0, 1);
    }
}

void WorldEdit::saveAll() { start_func
    if (!allWorlds) return;
    
    set<WorldEdit*>::iterator pos;
    set<WorldEdit*>::iterator end = allWorlds->end();

    for (pos = allWorlds->begin(); pos != end; ++pos) {
        assert(*pos);
        
        // Save it; cancel rest of process anytime cancel is hit
        if (!(*pos)->save()) return;
    }
}

void WorldEdit::setTitle(const string& newTitle, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (title != newTitle) {
        setHeaderModified();
        undo.storeUndoName(UndoBuffer::UNDO_WORLDNAME, 0, title, newTitle, srcWin);
        title = newTitle;
        if (browserNode) browserNode->changeName(newTitle);

        desktop->broadcastObjChange(OBJ_WORLD | OBJMOD_NAME, this, 0, 0, exWin);
    }
}

void WorldEdit::setStartScene(int sId, Window* srcWin, Window* exWin) throw_Undo { start_func
    if (sId != startingScene) {
        setHeaderModified();
        undo.storeUndoWorldStart(startingScene, srcWin);
        startingScene = sId;

        desktop->broadcastObjChange(OBJ_WORLD | OBJMOD_START, this, 0, 0, exWin);
    }
}

int WorldEdit::propertiesDialog(Window* srcWin, Window* exWin) { start_func
    string newTitle = title;
    int newStart = startingScene;

    if (WorldPropertiesDialog::create()->run(&newTitle, &newStart, this)) {
        try {
            undo.preUndoBlock();
            setTitle(newTitle, srcWin, exWin);
            setStartScene(newStart, srcWin, exWin);
            undo.postUndoBlock();
        }
        catch (UndoException& e) {
            return 0;
        }
        return 1;
    }

    return 0;
}

void WorldEdit::setHeaderModified() { start_func
    if (!headerModified) headerModified = 1;
}

void WorldEdit::setGlobalLinksModified() { start_func
    // Called often, so we won't actually clear the global links here
    globalLinksDone = 0;
    setHeaderModified();
}

int WorldEdit::isHeaderModified() const { start_func
    return headerModified;
}

int WorldEdit::isContentModified() const { start_func
    return contentModified;
}

Uint32 WorldEdit::saveHeader(FileWrite* file) throw_File { start_func
    file->writeStr(title);
    file->writeInt(startingScene);
    
    if (globalLinksDone) {
        assert(globalLinksCount == (int)globalLinks.size());
        file->writeInt(globalLinksCount);
        GlobalMap::iterator pos = globalLinks.begin();
        while (pos != globalLinks.end()) {
            file->writeStr((*pos).first);
            file->writeInt((*pos).second.first);
            file->writeStr((*pos).second.second);
        }
    }
    else {
        // -1 for 'global var list not present'
        file->writeInt((Uint32)-1);
    }
    
    return 1;
}

void WorldEdit::loadContent(FileRead* file) { start_func
    // (currently no content)
    delete file;
}

void WorldEdit::saveContent(FileWrite* file) { start_func
    // (currently no content)
}

void WorldEdit::saveSuccess() { start_func
    headerModified = contentModified = 0;
}

void WorldEdit::cachedContent(FileRead* file, int oldData) { start_func
    // (we don't cache)
    delete file;
}

int WorldEdit::save() { start_func
    if (saveFile->isNew()) {
        return saveAs();
    }
    
    try {
        // This will full save whenever appropriate
        saveFile->save();
    }
    catch (FileException& e) {
        guiErrorBox(e.details, errorTitleFile);
    }
    
    return 1;
}

int WorldEdit::saveAs() { start_func
    string* newFilename = NULL;
    if (filename) newFilename = new string(*filename);
    else newFilename = new string(blankString);

    if (fileSaveAs(FILETYPE_WORLD, *newFilename)) {
        try {
            saveFile->saveAs(newFilename);
        }
        catch (FileException& e) {
            guiErrorBox(e.details, errorTitleFile);
        }
        delete filename;
        filename = NULL;
        filename = newFilename;
    }
    else {
        delete newFilename;
        return 0;
    }
    
    return 1;
}

int WorldEdit::verifyWorld(WorldEdit* verify) { start_func
    if (!allWorlds) return 0;
    if (allWorlds->find(verify) != allWorlds->end()) return 1;
    return 0;
}

int WorldEdit::countWorlds() { start_func
    if (!allWorlds) return 0;
    return allWorlds->size();
}

WorldEdit* WorldEdit::listWorlds(int pos) { start_func
    if (!allWorlds) return NULL;
    if (pos < 0) return NULL;

    set<WorldEdit*>::iterator iter = allWorlds->begin();
    set<WorldEdit*>::iterator end = allWorlds->end();
    
    for (int numpos = 0; iter != end; ++iter, ++numpos) {
        assert(*iter);
        
        if (numpos == pos) return *iter;
    }
    
    return NULL;
}

// @TODO: start counting not at 1 but at last id assigned

int WorldEdit::unusedSceneId() const { start_func
    int expectId = 1;
    SceneIndex::const_iterator end = scenesById.end();
    for (SceneIndex::const_iterator pos = scenesById.begin(); pos != end; ++pos) {
        if ((*pos).first > expectId) break;
        ++expectId;
    }
    return expectId;
}

int WorldEdit::unusedTileSetId() const { start_func
    int expectId = 1;
    TileSetIndex::const_iterator end = tilesetsById.end();
    for (TileSetIndex::const_iterator pos = tilesetsById.begin(); pos != end; ++pos) {
        if ((*pos).first > expectId) break;
        ++expectId;
    }
    return expectId;
}

int WorldEdit::unusedAnimGroupId() const { start_func
    int expectId = 1;
    AnimGroupIndex::const_iterator end = animgroupsById.end();
    for (AnimGroupIndex::const_iterator pos = animgroupsById.begin(); pos != end; ++pos) {
        if ((*pos).first > expectId) break;
        ++expectId;
    }
    return expectId;
}

int WorldEdit::unusedScriptId() const { start_func
    int expectId = SUB_ENTITY_FIRST;
    ScriptIndex::const_iterator end = scriptsById.end();
    for (ScriptIndex::const_iterator pos = scriptsById.begin(); pos != end; ++pos) {
        if ((*pos).first > expectId) break;
        ++expectId;
    }
    return expectId;
}

int WorldEdit::unusedLayerId() const { start_func
    int expectId = 1;
    LayerIndex::const_iterator end = layersById.end();
    for (LayerIndex::const_iterator pos = layersById.begin(); pos != end; ++pos) {
        if ((*pos).first > expectId) break;
        ++expectId;
    }
    return expectId;
}

int WorldEdit::unusedSpawnId() const { start_func
    int expectId = 1;
    SpawnIndex::const_iterator end = spawnsById.end();
    for (SpawnIndex::const_iterator pos = spawnsById.begin(); pos != end; ++pos) {
        if ((*pos).first > expectId) break;
        ++expectId;
    }
    return expectId;
}

int WorldEdit::unusedFolderId() const { start_func
    int expectId = 1;
    FolderIndex::const_iterator end = foldersById.end();
    for (FolderIndex::const_iterator pos = foldersById.begin(); pos != end; ++pos) {
        if ((*pos).first > expectId) break;
        ++expectId;
    }
    return expectId;
}

void WorldEdit::indexFolder(FolderEdit* addFolder) { start_func
    assert(foldersById.find(addFolder->getId()) == foldersById.end());

    foldersById.insert(pair<int, FolderEdit*>(addFolder->getId(), addFolder));
}

void WorldEdit::deindexFolder(FolderEdit* remFolder) { start_func
    foldersById.erase(remFolder->getId());
}

FolderEdit* WorldEdit::findFolder(int fId) const { start_func
    FolderIndex::const_iterator loc = foldersById.find(fId);
    if (loc == foldersById.end()) return NULL;
    return (*loc).second;
}

void WorldEdit::indexLayer(LayerEdit* addLayer) { start_func
    assert(layersById.find(addLayer->getId()) == layersById.end());

    layersById.insert(pair<int, LayerEdit*>(addLayer->getId(), addLayer));
}

void WorldEdit::deindexLayer(LayerEdit* remLayer) { start_func
    layersById.erase(remLayer->getId());
}

LayerEdit* WorldEdit::findLayer(int fId) const { start_func
    LayerIndex::const_iterator loc = layersById.find(fId);
    if (loc == layersById.end()) return NULL;
    return (*loc).second;
}

void WorldEdit::indexSpawn(SpawnEdit* addSpawn) { start_func
    assert(spawnsById.find(addSpawn->getId()) == spawnsById.end());

    spawnsById.insert(pair<int, SpawnEdit*>(addSpawn->getId(), addSpawn));
}

void WorldEdit::deindexSpawn(SpawnEdit* remSpawn) { start_func
    spawnsById.erase(remSpawn->getId());
}

SpawnEdit* WorldEdit::findSpawn(int fId) const { start_func
    SpawnIndex::const_iterator loc = spawnsById.find(fId);
    if (loc == spawnsById.end()) return NULL;
    return (*loc).second;
}

void WorldEdit::indexScene(Scene* addScene) { start_func
    World::indexScene(addScene);
    
    SceneEdit* addEdit = dynamic_cast<SceneEdit*>(addScene);
    int count = addEdit->getLayerCount();
    for (int pos = 0; pos < count; ++pos) {
        LayerEdit* layer = addEdit->getLayerEdit(pos);
        indexLayer(layer);
        int countS = layer->getSpawnCount();
        for (int posS = 0; posS < countS; ++posS) {
            assert(layer->getSpawn(posS));
            indexSpawn(layer->getSpawn(posS));
        }
    }
}

void WorldEdit::deindexScene(Scene* remScene) { start_func
    SceneEdit* remEdit = dynamic_cast<SceneEdit*>(remScene);
    int count = remEdit->getLayerCount();
    for (int pos = 0; pos < count; ++pos) {
        LayerEdit* layer = remEdit->getLayerEdit(pos);
        int countS = layer->getSpawnCount();
        for (int posS = 0; posS < countS; ++posS) {
            deindexSpawn(layer->getSpawn(posS));
        }
        deindexLayer(layer);
    }
    
    World::deindexScene(remScene);
}

SaveLoad* WorldEdit::findItem(int blockType, int id) { start_func
    switch (blockType) {
        case WorldFileLoad::BLOCKTYPE_TILESET:
            return dynamic_cast<TileSetEdit*>(findTileSet(id));
            
        case WorldFileLoad::BLOCKTYPE_SCENE:
            return dynamic_cast<SceneEdit*>(findScene(id));
            
        case WorldFileLoad::BLOCKTYPE_ANIMGROUP:
            return dynamic_cast<AnimGroupEdit*>(findAnimGroup(id));
            
        case WorldFileLoad::BLOCKTYPE_SCRIPT:
            return dynamic_cast<ScriptEdit*>(findScript(id));
            
        case WorldFileLoad::BLOCKTYPE_FOLDER:
            return findFolder(id);
            
        default:
            return NULL;
    }
}
